///
/// 66.71 Sistemas Graficos
/// Framework de trabajo para el TP1
/// 2do Cuatrimestre 2010
///
/// info@sg6671.com.ar
///

#include <GL/glut.h>
#include <math.h>
#include <stdlib.h>
#include <iostream>

#include "vistaAerea.h"
#include "vistaLibre.h"
#include "vistaInterior.h"
#include "vistaExterior.h"

#include "camlookat.h"

#include "coord.h"

#include "bscerrada.h"
#include "bsanclada.h"
#include "bsabierta.h"

#include "bezier.h"

#include "superficie.h"
#include "superficierev.h"
#include "curva.h"

#include "textura.h"
#include "texels.h"

#include "discretizador.h"

#include "roca.h"
#include "vehiculo.h"

#include "mapa.h"
#include "editorRevoluciones.h"
#include "editorPerfil.h"
#include "iobmp.h"
#include "skybox.h"
#include <vector>

#include "ventana.h"
#include "panel.h"

#include "esfera.h"
#include "fisica/motor.h"

#define VISTAS_DISPONIBLES 4

static Vehiculo *vehiculo = 0;

class Main : public Ventana {
   Mapa *mapa;
   Esfera *esf;
   
   Skybox* skybox;
   Textura *roca;
   Textura *pasto;

   Textura *vehiculoSuperior;
   Textura *vehiculoLateral;

   Panel *panel;

   bool mostrarSuelo;
   bool mostrarAlambres;

   VistaAerea *vistaAerea;
   VistaLibre *vistaLibre;
   VistaInterior *vistaInterior;
   VistaExterior *vistaExterior;

   Vista *vista;
   Vista *vistasDisponibles[VISTAS_DISPONIBLES];

   int numCam;

   std::vector<Roca*> rocasLimitrofes;

   public:
   Main(int x, int y, int w, int h) : 
      Ventana(x, y, w, h), 
      mapa(0),
      esf(0), skybox(0), roca(0), pasto(0), panel(0),
      mostrarSuelo(true), mostrarAlambres(false),
      numCam(0) {
      }

   virtual void iniciar() {
      glEnable(GL_TEXTURE_2D);
   
      mapa = new Mapa(60, 60);
      vehiculo = new Vehiculo();

      vistaAerea = new VistaAerea(this->handler(), *vehiculo, mapa->ancho(), mapa->alto());
      vistaLibre = new VistaLibre(this->handler(), *vehiculo);
      vistaInterior = new VistaInterior(this->handler(), *vehiculo);
      vistaExterior = new VistaExterior(this->handler(), *vehiculo);
         
      vistasDisponibles[0] = vistaAerea;
      vistasDisponibles[1] = vistaLibre;
      vistasDisponibles[2] = vistaInterior;
      vistasDisponibles[3] = vistaExterior;

      IObmp io;
      Texels *tmp;

      tmp = &io.cargarBMP("../res/superior.bmp");
      vehiculoSuperior = new Textura(*tmp);
      delete tmp;
      vehiculoSuperior->setUso(GL_MODULATE);
      
      tmp = &io.cargarBMP("../res/lateral.bmp");
      vehiculoLateral = new Textura(*tmp);
      delete tmp;
      vehiculoLateral->setUso(GL_MODULATE);

      vehiculo->inicializarTexturas(vehiculoSuperior, vehiculoLateral);

      tmp = &io.cargarBMP("../res/roca.bmp");
      roca = new Textura(*tmp);
      delete tmp;
      roca->setUso(GL_MODULATE);

      mapa->setTexturaRoca(roca);

      tmp = &io.cargarBMP("../res/pasto.bmp");
      pasto = new Textura(*tmp);
      delete tmp;
      pasto->setUso(GL_MODULATE);
      mapa->setTexturaSuelo(pasto);

      skybox = new Skybox(mapa->ancho());

      panel = new Panel(*mapa, ancho(), alto());
      this->handler().signal_mouse().connect(sigc::mem_fun(panel, &Panel::editar));

      esf = new Esfera(1, 0.000005);

      std::vector<Coord> coordPared;
      coordPared.push_back(Coord(0, 0, -0));
      coordPared.push_back(Coord(0, 0, -1));
      coordPared.push_back(Coord(0, 0, -4));
      coordPared.push_back(Coord(0, 0, -5));


      std::vector<Coord> coordLimiteA;
      coordLimiteA.push_back(Coord(0, -10, 0));
      coordLimiteA.push_back(Coord(1, -10, 0));
      coordLimiteA.push_back(Coord(1, mapa->alto()+10, 0));
      coordLimiteA.push_back(Coord(0, mapa->alto()+10, 0));
      
      std::vector<Coord> coordLimiteB;
      coordLimiteB.push_back(Coord(-10, mapa->alto(), 0));
      coordLimiteB.push_back(Coord(-10, mapa->alto()-1, 0));
      coordLimiteB.push_back(Coord(mapa->ancho()+10, mapa->alto()-1, 0));
      coordLimiteB.push_back(Coord(mapa->ancho()+10, mapa->alto(), 0));
      
      std::vector<Coord> coordLimiteC;
      coordLimiteC.push_back(Coord(mapa->ancho(), -10, 0));
      coordLimiteC.push_back(Coord(mapa->ancho(), mapa->alto()+10, 0));
      coordLimiteC.push_back(Coord(mapa->ancho()-1, mapa->alto()+10, 0));
      coordLimiteC.push_back(Coord(mapa->ancho()-1, -10, 0));
      
      std::vector<Coord> coordLimiteD;
      coordLimiteD.push_back(Coord(-10, 0, 0));
      coordLimiteD.push_back(Coord(mapa->ancho()+10, 0, 0));
      coordLimiteD.push_back(Coord(mapa->ancho()+10, 1, 0));
      coordLimiteD.push_back(Coord(-10, 1, 0));
      
      Curva *a = new BsplineCerrada(coordLimiteA);
      Curva *b = new BsplineCerrada(coordLimiteB);
      Curva *c = new BsplineCerrada(coordLimiteC);
      Curva *d = new BsplineCerrada(coordLimiteD);

      Curva *perfilPared = new Bezier(coordPared);

      SuperficieRevolucion *limiteA = new SuperficieRevolucion(*a, *perfilPared);
      SuperficieRevolucion *limiteB = new SuperficieRevolucion(*b, *perfilPared);
      SuperficieRevolucion *limiteC = new SuperficieRevolucion(*c, *perfilPared);
      SuperficieRevolucion *limiteD = new SuperficieRevolucion(*d, *perfilPared);

      Discretizador disc;
      rocasLimitrofes.push_back(new Roca(*roca, *limiteA, 
            limiteA->revolucion(),
            limiteA->altura(), disc));
      rocasLimitrofes.push_back(new Roca(*roca, *limiteB, 
            limiteB->revolucion(),
            limiteB->altura(), disc));
      rocasLimitrofes.push_back(new Roca(*roca, *limiteC, 
            limiteC->revolucion(),
            limiteC->altura(), disc));
      rocasLimitrofes.push_back(new Roca(*roca, *limiteD, 
            limiteD->revolucion(),
            limiteD->altura(), disc));

      delete a;
      delete b;
      delete c;
      delete d;

      delete limiteA;
      delete limiteB;
      delete limiteC;
      delete limiteD;

      delete perfilPared;

      glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
      glEnable(GL_DEPTH_TEST);
      glColorMaterial(GL_FRONT, GL_DIFFUSE);


      //HEY!
      glCullFace(GL_BACK);
      glEnable(GL_CULL_FACE);

      skybox->setDaylightIllum();

      this->handler().signal_keyDown().connect(sigc::mem_fun(*this, &Main::control));

      vista = vistasDisponibles[0];
      vista->activar();
   }

   virtual void dibujar() {

      glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      ///////////////////////////////////////////////////
      // Escena 3D
      glViewport (0, 0, ancho(), alto()); 
      glMatrixMode (GL_PROJECTION);
      glLoadIdentity ();
      gluPerspective(60.0, (GLfloat) ancho()/(GLfloat) alto(), 0.10, 300.0);

      glMatrixMode(GL_TEXTURE);
      glLoadIdentity();
      glScalef(5, 5, 1);

      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();

      vista->ver();

      skybox->setPosicionLuces();

      //Debug del motor de fisica
      //Fisica::MotorFisico::getMotor()->drawDebug();


      //ATENCION!!!!
      //Para que una Superficie se vea real, hay que usar las luces!!
      //En conjunto, cada "Material" debe determinar que color debe reflejar
      //de forma Specular, Diffusa, y de Ambiente.
      //
      //Es muy tedioso andar definiendo el material para cada superficie
      //
      //Para simplificar, la siguiente instruccion que fue definida en init()
      //permite asociar las instruccion glColor a instrucciones glMaterial
      //Cada vez que hacemos un glColor, estamos definiendo el material
      //asociado con el color Difuso.
      //
      //        glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE);
      //
      //Si la superficie representa un objeto "rojo", se deberia usar
      //  glColor(1, 0, 0) tal como se hizo en otros TPs
      //
      //Ahora bien, cuando hay una Textura aplicada puede surgir dos cosas:
      //  - Usamos el color de la Textura (REMPLAZO) perdiendo asi el color del
      //     material y las luces. Es muy simple pero poco realista!.
      //  - Usamos el color de la Textura en conjunto con la "iluminacion" (MODULAR)
      //     Este es el default de la clase Textura, y modula los texels con la
      //     "iluminacion". Esta "iluminacion" es un color resultante entre la 
      //     combinacion de los colores de las luces y los colores de los materiales.
      //
      //     Para no "alterar" los colores de los texels, se suele usar un color "neutro"
      //     en los materiales, generalmente un gris o un blanco.
      //
      //     Pueden probar otros colores para que vean el resultado!
      //
      glEnable(GL_COLOR_MATERIAL);
      glColor3f(1, 1, 1);

      vehiculo->dibujar();

      if(mostrarSuelo) {
         mapa->dibujarSuelo();
         glColor3f(0.1, 0.1, 0.1);
         for(size_t i = 0; i < rocasLimitrofes.size(); ++i)
            rocasLimitrofes[i]->dibujar();
      
         glColor3f(1, 1, 1);
      }

      Discretizador d;

      list<Roca*>* rocas = mapa->getAlmacenRocas();
      list<Roca*>::iterator itrocas;
      for ( itrocas=rocas->begin() ; itrocas != rocas->end(); itrocas++ ){
         (*itrocas)->dibujar();
      }

      skybox->dibujar();
      esf->dibujar();
      panel->dibujar();
   
   }

   virtual void redimensionar(int w, int h) {
      Ventana::redimensionar(w, h);
      if(panel) panel->actualizar(w, h);
      glutPostRedisplay();
   }

   void control(unsigned char key, int x, int y) {
      switch (key) {
         case 'q':
            delete vistaAerea;
            delete vistaLibre ;
            delete vistaInterior;
            delete vistaExterior;
            
            delete roca;
            delete pasto;
            delete vehiculoSuperior;
            delete vehiculoLateral;

            delete esf;
            delete skybox;
            delete panel;

            delete vehiculo;
            delete mapa;
            
            for(size_t i = 0; i < rocasLimitrofes.size(); ++i)
               delete rocasLimitrofes[i];

            Fisica::MotorFisico::liberarRecursos();

            exit(0);
            break;

         case 'g':
            mostrarSuelo = not mostrarSuelo;
            glutPostRedisplay();
            break;

         case 'n':
            skybox->switchTime();
            glutPostRedisplay();
            break;

         case 'a':
            mostrarAlambres = not mostrarAlambres;
            glPolygonMode(GL_FRONT_AND_BACK, mostrarAlambres? GL_LINE : GL_FILL);
            mostrarAlambres? glDisable(GL_TEXTURE_2D) : glEnable(GL_TEXTURE_2D);
            break;

         case 'c':
            vista->desactivar();
            vista = vistasDisponibles[++numCam % VISTAS_DISPONIBLES];
            vista->activar();
            break;

         case 'b':
            mapa->removerObstaculos();
            break;

         default:
            break;
      }

   }


};


void play(int i) {
   vehiculo->update();

   Fisica::MotorFisico::getMotor()->play();
   if(i%2 == 0) {
      Ventana::refrescar();
   }
   glutTimerFunc(1500/60, play, ++i);
}


int main(int argc, char** argv)
{

   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  
   Main principal(0, 0, 600, 400);

   run(principal, "Sistemas Graficos");

   glutTimerFunc(500, play, 0);

   glutMainLoop();

   return 0;
}
